---
title: State management
description: A guide to working with state, how to read store values, ways to update a store using sample and .on, when to use derived stores, and handling undefined.
---

import Tabs from "@components/Tabs/Tabs.astro";
import TabItem from "@components/Tabs/TabItem.astro";
import SideBySide from "@components/SideBySide/SideBySide.astro";

# State management (#state-management)

All state management is done using [stores](/en/api/effector/Store), and the key feature is that stores do not have the usual `setState`. A store updates reactively when the [event](/en/api/effector/Event) it is subscribed to is triggered, for example:

```ts
import { createStore, createEvent } from "effector";

const $counter = createStore(0);

const incremented = createEvent();

// when incremented triggered, increase the counter by 1
$counter.on(incremented, (counterValue) => counterValue + 1);

incremented(); // $counter = 1
incremented(); // $counter = 2
```

If you are not familiar with events yet, just think of them as a trigger for updating the store. You can learn more about events on the [events page](/en/essentials/events), as well as [how to think in the effector paradigm and why events matter](/en/resources/mindset).

:::info{title="Data Immutability"}
If you store a [reference type](https://learn.javascript.ru/reference-type), such as an array or an object, in a store, then to update such a store you can either [use immer](/en/guides/best-practices#immer) or first create a new instance of that type:

<SideBySide>

<Fragment slot="left">

```ts wrap data-border="good" data-height="full" "const updatedUsers = [...users];" "const updatedUser = { ...user };"
// ✅ all good

// update array
$users.on(userAdded, (users, newUser) => {
  const updatedUsers = [...users];
  updatedUsers.push(newUser);
  return updatedUsers;
});

// update object
$user.on(nameChanged, (user, newName) => {
  const updatedUser = { ...user };
  updatedUser.name = newName;
  return updatedUser;
});
```

</Fragment>

  <Fragment slot="right">

```ts wrap data-border="bad" data-height="full"
// ❌ this is bad

$users.on(userAdded, (users, newUser) => {
  users.push(newUser); // mutate array
  return users;
});

$user.on(nameChanged, (user, newName) => {
  user.name = newName; // mutate object
  return user;
});
```

</Fragment>

</SideBySide>
:::

## Store creation (#store-creation)

Creating a store is done using the [`createStore`](/en/api/effector/createStore) method:

```ts
import { createStore } from "effector";

// creating a store with an initial value
const $counter = createStore(0);
// and with explicit typing
const $user = createStore<{ name: "Bob"; age: 25 } | null>(null);
const $posts = createStore<Post[]>([]);
```

:::tip{title="Naming stores"}
[The effector team suggests using the `$` prefix for stores](/en/guides/best-practices#naming), as it improves code readability and IDE autocompletion.
:::

## Reading values (#read-store-value)

As you already know, effector is a reactive state manager, and a [store](/en/api/effector/Store) is a reactive unit — reactivity is not created in a magical way. If you try to just use a store, for example:

```ts
import { createStore } from "effector";

const $counter = createStore(0);
console.log($counter);
```

You will see an obscure object with a bunch of properties, which effector needs for correct operation, but not the current value. To get the current value of a store, there are several ways:

1. Most likely you are also using some framework like [React](https://react.dev/), [Vue](https://vuejs.org/), or [Solid](https://docs.solidjs.com/), and in that case you need an adapter for this framework: [effector-react](/en/api/effector-react), [effector-vue](/en/api/effector-vue), or [effector-solid](/en/api/effector-solid). Each of these packages provides the `useUnit` hook to get data from a store and subscribe to its changes. When working with UI, this is the only correct way to read data:

<Tabs>
  <TabItem label="React">

```ts "useUnit"
import { useUnit } from 'effector-react'
import { $counter } from './model.js'

const Counter = () => {
  const counter = useUnit($counter)

  return <div>{counter}</div>
}
```

  </TabItem>
  <TabItem label="Vue">

```html "useUnit"
<script setup>
  import { useUnit } from "effector-vue/composition";
  import { $counter } from "./model.js";

  const counter = useUnit($counter);
</script>
```

  </TabItem>
  <TabItem label="Solid">

```ts "useUnit"
import { useUnit } from 'effector-solid'
import { $counter } from './model.js'

const Counter = () => {
  const counter = useUnit($counter)

  return <div>{counter()}</div>
}
```

  </TabItem>
</Tabs>

2. Since for building your logic outside the UI you may also need store data, you can use the [`sample`](/en/api/effector/sample) method and pass the store to `source`, for example:

```ts
import { createStore, createEvent, sample } from "effector";

const $counter = createStore(0);

const incremented = createEvent();

sample({
  clock: incremented,
  source: $counter,
  fn: (counter) => {
    console.log("Counter value:", counter);
  },
});

incremented();
```

A bit later we will also [discuss the `sample` method and how it can be used](/en/essentials/manage-states#store-updates) with stores.

3. You can subscribe to store changes using [`watch`](/en/api/effector/Store#methods-watch-watcher), however this is mainly used for debugging or for some custom integrations:

```ts
$counter.watch((counter) => {
  console.log("Counter changed:", counter);
});
```

4. The [`getState()`](/en/api/effector/Store#utility-methods-getState) method is generally used only for working with low-level APIs or integrations. Try not to use it in your code, as it may lead to race conditions:

```ts
console.log($counter.getState()); // 0
```

:::warning{title="Why not use getState?"}
For effector to work correctly with reactivity, it needs to build connections between units so that the data is always up to date. In the case of [`.getState()`](/en/api/effector/Store#utility-methods-getState), we essentially break this system and take the data from the outside.
:::

## Store updates (#store-updates)

As mentioned earlier, state updates happen through [events](/en/api/effector/Event). A store can subscribe to events using the [`.on`](/en/api/effector/Store#methods-on-trigger-reducer) method — good for primitive reactions, or the [`sample`](/en/api/effector/sample) operator — which allows updating a store based on another store or filtering updates.

:::info{title="What is sample?"}
The [`sample`](/en/api/effector/sample) method is an operator for creating connections between [units](/en/introduction/core-concepts#units). With it, you can trigger [events](/en/api/effector/Event) or [effects](/en/api/effector/Effect), as well as write new values into [stores](/en/api/effector/Store). Its algorithm is simple:

```ts
const trigger = createEvent();
const log = createEvent<string>();

sample({
  clock: trigger, // 1. when trigger fires
  source: $counter, // 2. take the value from $counter
  filter: (counter) => counter % 2 === 0, // 3. if the value is even
  fn: (counter) => "Counter is even: " + counter, // 4. transform it
  target: log, // 5. call and pass to log
});
```

:::

### Using `.on` (#update-store-via-on)

With [`.on`](/en/api/effector/Store#methods-on-trigger-reducer), we can update a store in a primitive way: event triggered → call the callback → update the store with the returned value:

```ts
import { createStore, createEvent } from "effector";

const $counter = createStore(0);

const incrementedBy = createEvent<number>();
const decrementedBy = createEvent<number>();

$counter.on(incrementedBy, (counterValue, delta) => counterValue + delta);
$counter.on(decrementedBy, (counterValue, delta) => counterValue - delta);

incrementedBy(11); // 0+11=11
incrementedBy(39); // 11+39=50
decrementedBy(25); // 50-25=25
```

### Using `sample` (#update-store-via-sample)

With the [`sample`](/en/api/effector/sample) method, we can update a store in a primitive way:

```ts
import { sample } from "effector";

sample({
  clock: incrementedBy, // when incrementedBy is triggered
  source: $counter, // take data from $counter
  fn: (counter, delta) => counter + delta, // call fn
  target: $counter, // update $counter with the value returned from fn
});

sample({
  clock: decrementedBy, // when decrementedBy is triggered
  source: $counter, // take data from $counter
  fn: (counter, delta) => counter - delta, // call fn
  target: $counter, // update $counter with the value returned from fn
});
```

At the same time, we also have more flexible ways — for example, updating a store only when **another store** has the required value. For example, perform a search only when `$isSearchEnabled` is `true`:

```ts
import { createStore, createEvent, sample } from "effector";

const $isSearchEnabled = createStore(false);
const $searchQuery = createStore("");
const $searchResults = createStore<string[]>([]);

const searchTriggered = createEvent();

sample({
  clock: searchTriggered, // when searchTriggered is triggered
  source: $searchQuery, // take data from $searchQuery
  filter: $isSearchEnabled, // continue only if search is enabled
  fn: (query) => {
    // simulate a search
    return ["result1", "result2"].filter((item) => item.includes(query));
  },
  target: $searchResults, // update $searchResults with the value returned from fn
});
```

Note that when passing a store into `target`, its previous value will be fully replaced with the value returned from `fn`.

### Updating from multiple events (#multiple-store-updates)

A store is not limited to a single event subscription — you can subscribe to as many events as needed, and multiple stores can subscribe to the same event:

```ts "categoryChanged"
import { createEvent, createStore, sample } from "effector";

const $lastUsedFilter = createStore<string | null>(null);
const $filters = createStore({
  category: "all",
  searchQuery: "",
});

const categoryChanged = createEvent<string>();
const searchQueryChanged = createEvent<string>();

// two different stores subscribing to the same event
$lastUsedFilter.on(categoryChanged, (_, category) => category);

sample({
  clock: categoryChanged,
  source: $filters,
  fn: (filters, category) => ({
    // following immutability principles
    ...filters,
    category,
  }),
  // the result of fn will replace the previous value in $filters
  target: $filters,
});

// store subscribing to two different events: searchQueryChanged and categoryChanged
sample({
  clock: searchQueryChanged,
  source: $filters,
  fn: (filters, searchQuery) => ({
    // following immutability principles
    ...filters,
    searchQuery,
  }),
  // the result of fn will replace the previous value in $filters
  target: $filters,
});
```

Here we subscribed two stores to the same `categoryChanged` event, and also subscribed the `$filters` store to another event `searchQueryChanged`.

## Derived stores (#derived-stores)

A derived store is computed **based on other stores** and **automatically updates** when those stores change. Imagine we have the following store:

```ts
import { createStore } from "effector";

const $author = createStore({
  name: "Hanz Zimmer",
  songs: [
    { title: "Time", likes: 123 },
    { title: "Cornfield Chase", likes: 97 },
    { title: "Dream is Collapsing", likes: 33 },
  ],
});
```

And we want to display the total number of likes, as well as the number of songs for this author. Of course, we could just use this store in the UI with the `useUnit` hook and calculate those values directly in the component. But this is not the right approach, because we would be mixing logic inside the component and spreading it throughout the application, making the code harder to maintain in the future. And if we wanted to reuse the same logic elsewhere, we’d have to duplicate the code. <br/>

In this case, the correct approach is to create derived stores based on `$author` using the [`combine`](/en/api/effector/combine) method:

```ts ins={13,15-17} "combine"
import { createStore, combine } from "effector";

const $author = createStore({
  name: "Hanz Zimmer",
  songs: [
    { title: "Time", likes: 123 },
    { title: "Cornfield Chase", likes: 97 },
    { title: "Dream is Collapsing", likes: 33 },
  ],
});

// total number of songs
const $totalSongsCount = combine($author, (author) => author.songs.length);
// total number of likes
const $totalLikesCount = combine($author, (author) =>
  author.songs.reduce((acc, song) => acc + song.likes, 0),
);
```

Each of these derived stores will automatically update whenever the original `$author` store changes.

:::warning{title="Important about derived stores!"}
Derived stores automatically update when the source stores change. They cannot be passed as a `target` in `sample` or subscribed to with `.on`.
:::

At the same time, there can be as many source stores as needed, which allows you, for example, to compute the current application state:

```ts "$isLoading, $isSuccess, $error"
import { combine, createStore } from "effector";

const $isLoading = createStore(false);
const $isSuccess = createStore(false);
const $error = createStore<string | null>(null);

const $isAppReady = combine($isLoading, $isSuccess, $error, (isLoading, isSuccess, error) => {
  return !isLoading && isSuccess && !error;
});
```

## `undefined` values (#void-values)

If you try to use a store value as `undefined` or put this value into a store:

```ts "return undefined;"
const $store = createStore(0).on(event, (_, newValue) => {
  if (newValue % 2 === 0) {
    return undefined;
  }

  return newValue;
});
```

you will encounter an error in the console:

```console
store: undefined is used to skip updates. To allow undefined as a value provide explicit { skipVoid: false } option
```

By default, returning `undefined` acts as a command "nothing happened, skip this update". If you really need to use `undefined` as a valid value, you must explicitly specify it with the `skipVoid: false` option when creating a store:

```ts "skipVoid: false"
import { createStore } from "effector";

const $store = createStore(0, {
  skipVoid: false,
});
```

:::info{title="The future of undefined"}
In upcoming versions this behavior will be changed. As practice shows, it’s usually better to just return the previous store value to avoid updating it.
:::

## Related API and docs (#related-api-and-docs)

- **API**

  - [`createStore`](/en/api/effector/createStore) — Method for creating a store
  - [`Store`](/en/api/effector/Store) — Description of a store and its methods

- **Articles**

  - [Core concepts](/en/introduction/core-concepts)
  - [Working with events](/en/essentials/events)
